Skip to content

Declarative calls - #5264

Merged
lutter merged 51 commits into
masterfrom
lutter/calls
Apr 9, 2024
Merged

lutter merged 51 commits into
masterfrom
lutter/calls

Conversation

@lutter

@lutter lutter commented Mar 5, 2024 •

Copy link
Copy Markdown
Contributor

Addresses #5262

This PR is concerned with two things:

  • refactoring trigger decoding and processing so that we get the decoded triggers all at once before any of them get processed
  • making eth calls for a block once before running handlers and plumbing things in a way that eth calls from handlers retrieve the results from memory

The results of eth_calls are injected back into the mappings via a BufferedCallCache which keeps the call results for the current block in memory. That way, mapping code does not need to change, and users can reap the benefit of declarative calls simply by, well, declaring them in the manifest. There's a small overhead in this approach as it requires calling a host function; compared to making an actual RPC call or just a database lookup, that's negligible.

When we do eth calls in bulk, we do one query against the store for all the calls we need (rather than one per call) For the calls for which we do not have anything in the database, we make RPC calls in parallel. By declaring eth calls, the time for all calls goes from the sum of the time the calls take to the max. Depending on the number of calls a subgraph makes for a block, that can be a considerable speedup. For one subgraph which did 10-20 eth calls per block, the time to process a block went from ~ 1.2s to ~ 200ms, a sixfold speedup. The environment where this was tested took 70-80ms per call.

Done:

  • Actually parallelize the calls
  • Deduplicate calls
  • Cache failed calls in memory to avoid duplicate calls
  • Better error messages when an eth_call fails

Still todo:

This PR is already pretty big; I will file the remaining open items as separate issues before merging this.

@lutter lutter self-assigned this Mar 5, 2024
@lutter
lutter force-pushed the lutter/calls branch 6 times, most recently from c239e44 to 39c161b Compare March 7, 2024 23:33
@lutter
lutter marked this pull request as ready for review March 9, 2024 05:31
@lutter
lutter force-pushed the lutter/calls branch 5 times, most recently from 66498aa to 10e4d8d Compare March 13, 2024 18:00
}
}

fn check_block(&self, block: &BlockPtr) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of "check_block" something like "clear_block_if_different" or something would make it more clear while reading callsites

for handler in &self.mapping.event_handlers {
for call in handler.calls.decls.as_ref() {
match self.mapping.find_abi(&call.expr.abi) {
// TODO: Handle overloaded functions by passing a signature

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something you will address on the next PR or would it be better to add an issue for it ?

/// ...)` where the `address` and the args are either `event.address` or
/// `event.params.<name>`.
///
/// The parser is pretty awful as it generates error messages that aren't

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for this

}
}

pub async fn process_trigger(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

@lutter
lutter force-pushed the lutter/calls branch 2 times, most recently from fcf9aab to 18e991b Compare March 20, 2024 19:57
@lutter

lutter commented Mar 25, 2024

Copy link
Copy Markdown
Contributor Author

Using a reliable eth provider, this ran cleanly in the integration cluster with no PoI divergences (test ran for 3 days)

lutter added 21 commits April 9, 2024 11:10
We need to make sure that the result of an eth call is entered into the
in-memory cache synchronously, before handlers are run to ensure that they
will find the call result. Only the part that writes it to the store can
happen asynchronously.
Log eth calls in a form that's easy to plug into other tools (like
foundry's cast)

Demote the more internal logging of encoded eth call data to trace
This also renames some of them, but has no functional change
We need to relate requests to EthereumCalls that we were given even when
the requests are reshuffled from the initial calls
This tries to avoid having to return errors for each call separately from
contract_calls, while preserving the distinction between errors that
indicate reorgs nad other errors.
The DecoderHook now relies on EthereumAdapter's contract_calls method. With
that, cached call results are looked up with one query from the store. Call
results that are not cached are retrieved with eth_calls made in parallel
to the ethereum node.
The DecoderHook and the RuntimeAdapter for ethereum need to share the same
call cache, a separate one for each subgraph, since the BufferedCallCache
needs to be a different one for each subgraph - if subgraphs shared a
BufferedCallCache, they would clobber each others declared calls.
@kasparkallas

kasparkallas commented Apr 14, 2025 •

Copy link
Copy Markdown

Hi @lutter

This looks like an useful feature that could increase our subgraph indexing speed a ton. So thanks!

Question, if a declarative call is defined in the manifest, are these RPC calls always executed eagerly, regardless if the handler actually called it?

The reason I ask this is because some of our handlers have conditional logic to decide whether to execute the RPC call or not. I'm just wondering if I'm now in the position of choosing whether to optimize the RPC calls' performance or the number of RPC calls.

@kasparkallas

Copy link
Copy Markdown

Link to "declared eth_call" docs to add more detail to this PR: https://thegraph.com/docs/en/subgraphs/developing/creating/advanced/#declared-eth_call

@lutter

lutter commented Apr 15, 2025

Copy link
Copy Markdown
Contributor Author

Question, if a declarative call is defined in the manifest, are these RPC calls always executed eagerly, regardless if the handler actually called it?

The reason I ask this is because some of our handlers have conditional logic to decide whether to execute the RPC call or not. I'm just wondering if I'm now in the position of choosing whether to optimize the RPC calls' performance or the number of RPC calls.

Yes, the declared calls are made no matter what. Whether declared calls in your situation will speed things up or not will depend on how likely it is that a call is made/not made, but since declared calls are all made in parallel, the cost for unnecessary calls (within reason) is very small

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants